#include "cpu_info.h"
#include <cpuid.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

void build_cpu_info_string(char *buf, uint32_t r0, uint32_t r1, uint32_t r2, uint32_t r3)
{
    memcpy(buf, (char *)&r0, 4);
    memcpy(buf + 4, (char *)&r1, 4);
    memcpy(buf + 8, (char *)&r2, 4);
    memcpy(buf + 12, (char *)&r3, 4);
    return;
}

void get_cpu_info()
{
    uint32_t eax, ebx, ecx, edx;
    __cpuid(0, eax, ebx, ecx, edx);

    char proc_vendor_string[17] = { 0 };
    build_cpu_info_string(proc_vendor_string, ebx, edx, ecx, 0);
    printf("Vendor ID: %s\n", proc_vendor_string);

    char proc_brand_string[48];
    __cpuid(0x80000002, eax, ebx, ecx, edx);
    build_cpu_info_string(proc_brand_string, eax, ebx, ecx, edx);
    __cpuid(0x80000003, eax, ebx, ecx, edx);
    build_cpu_info_string(proc_brand_string + 16, eax, ebx, ecx, edx);
    __cpuid(0x80000004, eax, ebx, ecx, edx);
    build_cpu_info_string(proc_brand_string + 32, eax, ebx, ecx, edx);
    printf("Brand: %s\n", proc_brand_string);

    __cpuid(0x01, eax, ebx, ecx, edx);
    uint32_t displ_family = ((eax >> 8) & 0xF);
    if (displ_family == 0x0F) {
        displ_family += ((eax >> 20) & 0xFF);
    }
    uint32_t displ_model = ((eax >> 4) & 0xF);
    if (displ_family == 0x06 || displ_family == 0x0F) {
        displ_model += ((eax >> 12) & 0xF0);
    }
    printf("DisplayFamily_DisplayModel: %.2X_%.2XH\n", displ_family, displ_model);
    printf("Stepping ID: %u\n", (eax & 0xF));

    if (strcmp(proc_vendor_string, "GenuineIntel") == 0) {
        printf("Performance monitoring version: %u\n", get_pmu_verison());
        printf("Number of general-purpose performance counters: %u\n", get_pmu_counters());
        printf("Bit widths of general-purpose performance counters: %u\n", get_pmu_width());
    } else {
        printf("Unsupported CPU\n");
    }
    
    return;
}

int get_pmu_verison()
{
    uint32_t eax, ebx, ecx, edx;
    __cpuid(0x0a, eax, ebx, ecx, edx);
    return (int)(eax & 0xff);
}

int get_pmu_counters()
{
    uint32_t eax, ebx, ecx, edx;
    __cpuid(0x0a, eax, ebx, ecx, edx);
    return (eax >> 8) & 0xff;
}

int get_pmu_width()
{
    uint32_t eax, ebx, ecx, edx;
    __cpuid(0x0a, eax, ebx, ecx, edx);
    return (eax >> 16) & 0xff;
}
